/**
 *
 * The MIT License
 *
 * Copyright (c) 2008 Board of Trustees, Leland Stanford Jr. University. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.macdadi.core.client.ui;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;

/**
 * A base class that should work for most tree node editors.
 * It provides a clickable image (icon) with the title text as a
 * tooltip. When clicked, a dialog box is displayed that contains
 * a form for collecting user input, etc. Concrete sub-classes will
 * provide a Widget that implements such a form, by overriding the
 * @ref getInputWidget(Object userObject)
 * method as appropriate.
 *
 * This class implements the AsyncCallback interface - on success it
 * executes the onSuccess command, if one was supplied to the constructor,
 * and on failure it displays an error message. This is appropriate for
 * many common cases where the input form's event handlers result in an RPC
 * call to the domain access layer on the server-side.
 *
 * Three simple icon images are provided (edit, add, remove), or you can
 * use anything you like. The icons should be 16x16 pixels in size.
 *
 * The icons are set as images within an HTML BUTTON element, and the BUTTON
 * element has a style name: macdadi-EditableTreeControl
 *
 * Typical use: Create a sub-class, implement the abstract getInputWidget method,
 * and override the matchesItem method to select appropriate nodes.
 * 
 * @author Ben Suter <ben.suter@gmail.com>
 */
public abstract class DefaultItemEditor implements EditableTree.ItemEditor, AsyncCallback
{
    public static final String iconPathEdit = GWT.getModuleBaseURL() + "images/tree-edit.png";
    public static final String iconPathAdd = GWT.getModuleBaseURL() + "images/tree-add.png";
    public static final String iconPathRemove = GWT.getModuleBaseURL() + "images/tree-remove.png";

    protected static final String imgStart = "<img src=\"", imgMid = "\" title=\"", imgEnd = "\" >";
    protected static final String style = "macdadi-EditableTreeControl";
    protected Button btn;
    protected ClickListener cl;
    protected DialogBox inputPanel;
    protected TreeItem item;
    protected Command onSuccess;

    /**
     * Return some input widget, typically a form with input fields and buttons to submit/reset.
     * Also provide event handlers for any such buttons.
     *
     * @param userObject the Object attached to the current TreeItem, typcially contains the domain
     *                   to be edited, so the returned widget should display controls to
     *                   edit this object
     * @return a widget allowing the user to edit the node's user object, typically this is a form
     */
    protected abstract Widget getInputWidget(Object userObject);

    /**
     * By default, matches all TreeItems
     * @param item the TreeItem against which this is to be matched
     * @return true if this editor should be associated with the specified item
     */
    public boolean matchesItem(TreeItem item) {
        return true;
    }

    public DefaultItemEditor(String iconPath, String title, Command onSuccess) {
        this.onSuccess = onSuccess;
        buildControls(iconPath, title);
        buildForm(title);
    }

    protected void buildControls(String iconPath, String title) {
        btn = new Button();
        btn.setHTML(imgStart + iconPath + imgMid + title + imgEnd);
        btn.setStyleName(style);
        btn.addClickListener(getClickListener());
    }

    protected void buildForm(String title) {
        inputPanel = new DialogBox(true, true);
        inputPanel.setText(title);
    }

    protected ClickListener getClickListener() {
        if (cl == null) {
            cl = new ClickListener() {
                public void onClick(Widget sender) {
                    showEditPanel(sender.getAbsoluteLeft() + 20, sender.getAbsoluteTop());
                }
            };
        }
        return cl;
    }

    protected void showEditPanel(int x, int y) {
        inputPanel.setWidget(getInputWidget(item.getUserObject()));
        inputPanel.setPopupPosition(x, y);
        inputPanel.show();
    }

    /**
     * The widget is, for now, always a Button, because that is the only widget
     * that I found that would still properly generate click events when inserted
     * dynamically into the DOM.
     * 
     * @return a Widget that will be displayed in-line with the selected tree item
     */
    public Widget getWidget() {
        return btn;
    }

    // TODO: is this just poorly named, or should it work differently? For now, it just means
    // that the "control" (aka button) has been added to the tree. This particular implementation of
    // ItemEditor DEFERS editing per se. It displays a control (button), which when clicked will display
    // a full input form.
    public void startEditing(TreeItem item) {
        this.item = item;
    }

    /**
     * Tells this editor to stop the editing process and hide disappear.
     *
     * Maybe in the future this method returns a boolean, which if false indicates to the
     * tree that it should not (yet) switch to a different node?
     */
    public void stopEditing() {
        inputPanel.hide();
        item = null;
    }


    /**
     * Displays an error message
     *
     * @param caught  the exception 
     */
    public void onFailure(Throwable caught) {
        displayError("The desired change did not succeed. An problem occurred: " + caught.getMessage(), caught);
    }

    /**
     * If the result is null this is interpreted as failure. If the
     * result is not null, but is a Boolean and is false, this is also
     * interpreted as failure. In the case of failure, an error message
     * is displayed.
     *
     * In the case of success, the editor is closed and the onSuccess command
     * is executed.
     *
     * @param result the result of the async call
     */
    public void onSuccess(Object result) {
        boolean itWorked = false;
        if ( result != null ) {
            itWorked = true;
            if ( result instanceof Boolean )
                itWorked = ((Boolean) result).booleanValue();
        }

        if ( itWorked ) {
            inputPanel.hide();
            if (onSuccess != null)
                DeferredCommand.addCommand(onSuccess);
        } else {
            displayError("The desired change did not succeed", null);
        }
    }

    /**
     * Displays an error message to the user.
     * Currently this just logs to the GWT console, but it should
     * in the future show the message to the user as feedback.
     * @param message what went wrong
     * @param error  optionally, an exception
     */
    public void displayError(String message, Throwable error) {
        // TODO: show the message as user feedback, on the visible editor
        GWT.log(message, error);
    }

    /**
     * Generates a simple input form for editing a single
     * text value. Useful for testing and debugging.
     *
     * @param initialText the text to display in the input field
     * @return  a form with a text input and two buttons
     */
    public Widget sampleInputForm(String initialText) {
        VerticalPanel vp = new VerticalPanel();
        final TextBox tb = new TextBox();
        if (initialText != null)
            tb.setText(initialText);

        vp.add(tb);
        HorizontalPanel hp = new HorizontalPanel();
        Button a = new Button("Apply");
        a.addClickListener(new ClickListener() {
            public void onClick(Widget sender) {
                Window.alert("Applying ...");
            }
        });
        hp.add(a);
        Button r = new Button("Reset");
        hp.add(r);
        r.addClickListener(new ClickListener() {
            public void onClick(Widget sender) {
                tb.setText("");
            }
        });
        vp.add(hp);
        return vp;
    }
}
